﻿using AdvVerifyModels.Entities;
using AdvVerifyModels.Utils;
using HWModule;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace AdvVerifyGUI.Job;

class Publisher : IDisposable
{
    readonly MessageQueue<PatternResult> queue;
    readonly JobRunOptions options;

    readonly string alignBasePath;
    readonly string addrBasePath;
    readonly string rawBasePath;

    readonly StreamWriter writer;

    readonly Dictionary<string, int> names = [];

    int high;
    int align;
    int mp;

    public Publisher(MessageQueue<PatternResult> queue, JobRunOptions options)
    {
        this.queue = queue;
        this.options = options;

        if (options.OutputDirectory != null)
        {
            alignBasePath = Path.Join(options.OutputDirectory, "align");
            addrBasePath = Path.Join(options.OutputDirectory, "addr");
            rawBasePath = Path.Join(options.OutputDirectory, "raw");
            Directory.CreateDirectory(alignBasePath);
            Directory.CreateDirectory(addrBasePath);
            Directory.CreateDirectory(rawBasePath);

            var path = Path.Combine(options.OutputDirectory, "results.csv");
            // FIXME
            if (!File.Exists(path))
            {
                writer = new StreamWriter(path);
                writer.WriteLine(",ix,iy,x,y,name,result,addr. dx, addr. dy,dx,dy,addr. score,score,addr. threshold,threshold");
            }
        }
    }

    public string PublishHigh(ImagingResult pImg)
    {
        high += 1;

        var name = $"Align-High {high}";
        return PublishAlign(high, name, pImg);
    }

    public string PublishAlign(ImagingResult pImg, ImageData raw)
    {
        align += 1;

        var name = $"Align-SEM {align}";
        Save(rawBasePath, name, pImg.Coordinate, raw);
        return PublishAlign(align, name, pImg);
    }

    public string PublishAddr(ImagingResult pImg, ImageData raw)
    {
        var name = $"A{mp + 1}";
        string path = Save(addrBasePath, name, pImg.Coordinate, pImg.Image);
        Save(rawBasePath, name, pImg.Coordinate, raw);

        var pattern = pImg.Pattern;
        if (pImg.Score >= pattern.Addressing.Threshold)
        {
            return path;
        }

        mp += 1;

        var values = new List<(string, double?)>();
        foreach (var spec in pImg.Pattern.CDPoints)
        {
            values.Add((spec.Name, null));
        }
        if (values.Count == 0)
        {
            values.Add(($"P{mp}", null));
        }

        queue.Send(new PatternResult
        {
            Id = options.Id,
            Index = mp,
            Coordinate = pImg.Coordinate,
            Threshold = pattern.Reference.Threshold,
            AddressingScore = pImg.Score,
            AddressingThreshold = pattern.Addressing.Threshold,
            Values = values
        });

        var addr = pImg.Pattern.Addressing;
        var pixelSize = GetPixelSize(addr.ScanSettings);
        if (writer != null)
        {
            foreach (var (param, _) in values)
            {
                var c = pImg.Coordinate;
                writer.WriteLine($"{mp},{c.X},{c.Y},{c.Dx},{c.Dy},{param},-," +
                    $"{pImg.MatchDx * pixelSize},{pImg.MatchDy * pixelSize},-,-," +
                    $"{pImg.Score},-,{pattern.Addressing.Threshold},{pattern.Reference.Threshold}");
            }
        }

        return path;
    }

    public string PublishMp(ImagingResult pImg, IReadOnlyList<(ImageData, double)> results)
    {
        mp += 1;

        var name = $"P{pImg.Pattern.Ordinal}";
        var n = pImg.Pattern.CDPoints.Count;
        (string, double?)[] values;
        if (n == 0)
        {
            values = [(name, null)];
        }
        else
        {
            values = new (string, double?)[n];
            if (results.Count == 0)
            {
                // AMP skipped
                for (var i = 0; i < results.Count; i++)
                {
                    values[i] = (pImg.Pattern.Names[i], null);
                }
            }
            else
            {
                for (var i = 0; i < n; i++)
                {
                    var (_, v) = results[i];
                    values[i] = (pImg.Pattern.Names[i], v);
                }
            }
        }

        Send(mp, pImg, values);
        var path = Save(rawBasePath, name, pImg.Coordinate, pImg.Image);
        WriteLines(mp, pImg, values);
        return path;
    }

    public string SaveResultImage(string name, Coordinate c, ImageData img)
    {
        return Save(options.OutputDirectory, name, c, img);
    }

    string PublishAlign(int index, string name, ImagingResult pImg)
    {
        var values = new (string, double?)[] { (name, null) };
        Send(index, pImg, values);
        WriteLines(index, pImg, values);
        return Save(alignBasePath, name, pImg.Coordinate, pImg.Image);
    }

    void Send(int index, ImagingResult pImg, IReadOnlyList<(string, double?)> values)
    {
        // FIXME
        if (options.Id < 0)
        {
            return;
        }

        var pattern = pImg.Pattern;
        queue.Send(new PatternResult
        {
            Id = options.Id,
            Index = index,
            Coordinate = pImg.Coordinate,
            Score = pImg.Score,
            Threshold = pattern.Reference.Threshold,
            AddressingScore = pImg.AddressingScore,
            AddressingThreshold = pattern.Addressing.Threshold,
            Values = values
        });
    }

    string Save(string basePath, string name, Coordinate c, ImageData img)
    {
        if (options.OutputDirectory == null)
        {
            return "";
        }

        var fn = $"{name}-{c.X}-{c.Y}";
        if (!names.TryGetValue(fn, out int value))
        {
            names[fn] = 1;
        }
        else
        {
            var n = value + 1;
            names[fn] = n;
            fn += "-" + n;
        }
        string path = Path.Join(basePath, fn + ".png");
        Save(path, img);
        return path;
    }

    void WriteLines(int index, ImagingResult pImg, IReadOnlyList<(string, double?)> values)
    {
        if (writer == null)
        {
            return;
        }

        var pattern = pImg.Pattern;
        var addrPixelSize = GetPixelSize(pattern.Addressing.ScanSettings);
        var pixelSize = GetPixelSize(pattern.Reference.ScanSettings);
        foreach (var (name, v) in values)
        {
            var c = pImg.Coordinate;
            writer.WriteLine($"{index},{c.X},{c.Y},{c.Dx},{c.Dy},{name},{ToString(v)}," +
                $"{FormatAddrMatch(pImg.AddrMatchDx)},{FormatAddrMatch(pImg.AddrMatchDx)}," +
                $"{pImg.MatchDx * pixelSize},{pImg.MatchDy * pixelSize}," +
                $"{ToString(pImg.AddressingScore)},{pImg.Score}," +
                $"{pattern.Addressing.Threshold},{pattern.Reference.Threshold}");
        }

        string FormatAddrMatch(double? value)
        {
            if (value.HasValue)
            {
                return (pImg.AddrMatchDx.Value * addrPixelSize).ToString();
            }
            else
            {
                return "-";
            }
        }
    }

    static double GetPixelSize(ScanSettings settings)
    {
        return settings.ScanWidth == 0 ? 0 : settings.FOV / settings.ScanWidth * 1000;
    }

    static string ToString(double? v)
    {
        return v.HasValue ? v.Value.ToString() : "-";
    }

    static string ToString(int? v)
    {
        return v.HasValue ? v.Value.ToString() : "-";
    }

    static void Save(string path, ImageData img)
    {
        using var stream = new FileStream(path, FileMode.Create);
        var encoder = new PngBitmapEncoder();

        var (w, h, data, _) = img;
        BitmapSource image = BitmapSource.Create(
            w,
            h,
            96,
            96,
            PixelFormats.Gray8,
            null,
            data,
            w);
        encoder.Frames.Add(BitmapFrame.Create(image));
        encoder.Save(stream);
    }

    public void Dispose()
    {
        writer?.Dispose();
    }
}